package com.uraurora.crescent.tuple;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;

/**
 * 元组抽象类
 * @author ：Sei
 * @date ：Created in 2020/1/9 22:53
 */
public abstract class AbstractTuple implements ITuple, Iterable<Object> {

    private final Object[] values;

    private final Class<?>[] types;

    AbstractTuple(Object[] values){
        this.values = Objects.requireNonNull(values).clone();
        this.types = obj2clazz(values);
    }

    @Override
    public Object get(int index) {
        rangeCheck(index);
        return values != null ? values[index] : null;
    }

    @Override
    public <T> T get(int index, Class<T> type){
        rangeCheck(index);
        return values != null ? type.cast(values[index]) : null;
    }

    private void rangeCheck(int index) {
        if (index >= size()) {
            throw new IndexOutOfBoundsException(indexErrorMsg(index));
        }
    }


    @Override
    public  Class<?> getType(int index){
        rangeCheck(index);
        return types != null ? types[index] : null;
    }

    @Override
    public Class<?>[] getTypes(){
        return this.types.clone();
    }


    @Override
    public int size(){
        return values.length;
    }

    @Override
    public <E> boolean contains(E element) {
        for (Object o : this) {
            if (Objects.equals(element, o)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object[] toArray(){
        return this.values.clone();
    }

    @Override
    public Iterator<Object> iterator() {
        return new TupleIterator(0);
    }

    private class TupleIterator implements Iterator<Object>{

        private int cursor;

        TupleIterator(int cursor){
            this.cursor = cursor;
        }

        @Override
        public boolean hasNext() {
            return cursor != size();
        }

        @Override
        public Object next() {
            try{
                return get(cursor++);
            }
            catch (IndexOutOfBoundsException e){
                throw new IndexOutOfBoundsException(e.getMessage());
            }

        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        AbstractTuple objects = (AbstractTuple) o;
        return Arrays.equals(values, objects.values) && Arrays.equals(types, objects.types);
    }

    @Override
    public int hashCode() {
        int result = Arrays.hashCode(values);
        result = 31 * result + Arrays.hashCode(types);
        return result;
    }

    @Override
    public String toString() {
        return "Tuple{" +
                "values=" + Arrays.toString(values) +
                ", types=" + Arrays.toString(types) +
                '}';
    }

    //<editor-fold desc="static methods">

    private static Class<?>[] obj2clazz(Object[] objects){
        Class<?>[] res = new Class<?>[objects.length];
        for(int i=0;i<objects.length;i++){
            if (objects[i] == null) {
                throw new NullPointerException();
            }
            res[i] = objects[i].getClass();
        }
        return res;
    }

    private String indexErrorMsg(int index){
        return "index:" + index + ", size:" + size();
    }


    //</editor-fold>
}
